//***********************************************************************************
// MediaFrame is an Open Source streaming media platform in Java 
// which provides a fast, easy to implement and extremely small applet 
// that enables to view your audio/video content without having 
// to rely on external player applications or bulky plug-ins.
//
//--------------------------------------------------------------------------------------
//
// We changed a lot of code and added a lot of functionality.
// This includes, but not limited to, the following changes:
// 1. The project was renamed to MediaFrame;
// 2. The connection speed detection procedure was added;
// 3. The JavaScript API functions were added;
// 4. The pre and post image support functionality was added;
// 5. The ability to save movie into the local disk was added;
// 6. The inner buffer for a movie file was added;
// 7. The click-through functionality was added;    
// 8. The .zip files support was added;    
// 9. The realtime feedback agent functionality was added.    
// For the full list of the current functionality please visit the following web page:
// http://mediaframe.org/
//    
// 06 Jul 2002 - 19 Dec 2004 Konstantin Belous, Oleg Lebedev
//
//--------------------------------------------------------------------------------------
//
// I changed a lot of the initialization-code. The class is now able to handle some
// parameters in the applet-tag:
//
// param		option/value (<> == default)
//
// FILENAME		The relative/absolute file/url of the file/stream/cgi-bin
// AUDIO		(no/<yes>) 
// CONTROLS		(<no>/yes) full will enable ONE stop button...
// CUTTING		(<no>/yes) will cut P/B-frames if unable to keep up with stream
//
// I replaced the 'MPEG_scan'-class with a 'CutBuffer' that is a bit more specialized
// in handling streams. For this file this means that all calls to this 'scanner' are
// replaced with simular calls to other classes.
//
// -- Ron -- (29-04-2002)
//
//***********************************************************************************
//
//				MPEG1.java (originally MPEG_Play.java.jit)
//
// This is the main class file for the JIT version of the Java MPEG inline player. In
// contrast to the original version the decoded pictures are not saved for a later
// play step. Instead the pictures are shown already during decoding step and it is
// assumed that the JIT (Java just-in-time compiler) is fast enought to produce about
// 5 to 7 frames per second.
//
// The class "Element" can hold one picture. An array "EL_ARR[]" holds "MAXELEMENT"
// of these "Element"s. It is used in wrap around manner. To store some pictures is
// necessary because in MPEG file the coding order is not the display order. (See also:
//
//	http://rnvs.informatik.tu-chemnitz.de/~ja/MPEG/HTML/mpeg_tech.html
//
// The class "MPEG_Play" is the main Applet. It reads the input MPEG stream and starts
// one thread:
//			- ScanThread : Scanning of the MPEG Stream
//
// In case of an application a method "main()" is implemented.
//--------------------------------------------------------------------------------------
//
//		Joerg Anders, TU Chemnitz, Fakultaet fuer Informatik, GERMANY
//		ja@informatik.tu-chemnitz.de
//
//--------------------------------------------------------------------------------------
//
// This program is free software; you can redistribute it and/or modify it under the
// terms of the GNU General Public License as published by the Free Software
// Foundation; either version 2 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE. See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with this
// program; (See "LICENSE.GPL"). If not, write to the Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//
//--------------------------------------------------------------------------------------
//
// If the program runs as Java applet it isn't "interactive" in the sense of the GNU
// General Public License. So paragraph 2c doesn't apply.
//
//**************************************************************************************

package mediaframe.mpeg1;

import java.awt.*; // Image, Frame, BorderLayout
import java.awt.event.*;
import java.io.*; // DataInputStream, IOException
import java.lang.reflect.InvocationTargetException;
import java.net.*; // URL, MalformedURLException
import java.applet.*;
import java.awt.image.*; // ColorModel, DirectColorModel, MemoryImageSource

import mediaframe.mpeg1.audio.player.*; // Player

import java.util.Date;
import java.util.zip.*; // Zip
import java.util.Enumeration; // decompression

import javax.swing.JApplet;

import com.lightminds.appletserver.annotations.AppletServerResourcePath;

import mediaframe.mpeg1.controlpanel.IControlPanel; // Control Panel


/**
 * The class "Element" stores the information of a single frame.
 * Exactly "MAXELEMENT" objects of this class exist 
 * in an array "EL_ARR[]" which is used in wrap around manner.
 * The pixels are given as YUV values and they are then translated 
 * into the Java-AWT RGB model.
 */
class Element {
	/** Notice the frame number (display order). */
	public int Frame_idx; 
	/** The type of the frame (I, B or P). */
	public int Frame_type;
	/** The pixels in Java RGB model. */
	public int Pix_Map[];
	/** The width of the pixels array. */
	private int pixel_map_width;
	/** The height of the pixels array. */
	private int pixel_map_height; 
	/** The coefficient for Y' value for colors calculation (1.023*2^12). */
	private static final int CY_FAC = 0x12A1;
	/** The coefficient for Cr value for R color calculation (1.402*2^12). */
	private static final int CR_FAC = 0x198B; 
	/** The coefficient for Cb value for B color calculation (1.772*2^12). */
	private static final int CB_FAC = 0x204B;
	/** The coefficient for Cr value for G color calculation (0.71414 * 2^12). */
	private static final int CR_DIFF_FAC = 0xD02;
	/** The coefficient for Cb value for G color calculation (0.34414 * 2^12). */
	private static final int CB_DIFF_FAC = 0x645;
	/** The width dimension of the frame pixels array. */
	private int w;
	/** The height dimension of the frame pixels array. */
	private int h;
	/** Flag, indicates that the frame should be scaled by width. */
	private boolean scaled_x = true;
	/** Flag, indicates that the frame should be scaled by height. */
	private boolean scaled_y = true;
	/** The width screen dimension. */
	private int sc_x;
	/** The height screen dimension. */
	private int sc_y;
	/** The x step (equals to w / sc_x) between pixel columns. */
	private int step_x;
	/** The y step (equals to h / sc_y) between pixel rows. */
	private int step_y;
	/** Presentation timestamp (in milliseconds). */
	private long pts;
	/** The amount of frames per second. */
	private int picRate = 0;

	/**
	 * Constructs an <code>Element</code> object.
	 * Notices the frame sizes and produces an image and an array to store the RGB pixel values. 
	 * The Applet (app) object is necessary to call the "createImage" routine.
	 * @param app the applet (app) object.
	 * @param cm the color model of the screen (RGB).
	 * @param w the width dimension of the frame pixels array.
	 * @param h the height dimension of the frame pixels array.
	 * @param o_w the width dimension of the movie frame.
	 * @param o_h the height dimension of the movie frame.
	 * @param sc_x the width screen dimension.
	 * @param sc_y the height screen dimension.
	 */
	Element(
		MPEG1 app,
		ColorModel cm,
		int w,
		int h,
		int o_w,
		int o_h,
		int sc_x,
		int sc_y) {
			
		pixel_map_width = w > sc_x ? w : sc_x;
		pixel_map_height = h > sc_y ? h : sc_y;
			
		Pix_Map = new int[pixel_map_width * pixel_map_height]; // memory for the translated RGB values
		this.w = w;
		this.h = h;
		this.sc_x = sc_x;
		this.sc_y = sc_y;

		if (sc_x != w) {
			scaled_x = true;
			step_x = (w << 8) / sc_x;
		}
		if (sc_y != h) {
			scaled_y = true;
			step_y = (h << 8) / sc_y;
		}

		if (app.movieScreen.Picture == null) {
			app.movieScreen.source =
				new MemoryImageSource(sc_x, sc_y, cm, Pix_Map, 0, pixel_map_width);
			app.movieScreen.colorModel = cm;
			app.movieScreen.scanSize = pixel_map_width;
			app.movieScreen.source.setAnimated(true);
			app.movieScreen.source.setFullBufferUpdates(true);
			app.movieScreen.Picture = app.createImage(app.movieScreen.source);
			app.movieScreen.source.newPixels();
		}

		if (app.movieScreen.screen == null) {
			app.movieScreen.screen = app.movieScreen.createImage(sc_x, sc_y);
		}

	}

	/**
	 * Gets the time in milliseconds that this element is to be displayed.
	 * @return the time in milliseconds that this element is to be displayed.
	 */
	protected long getPts() {
		return pts;
	}

	/**
	 * Sets the time in milliseconds that this element is to be displayed.
	 * @param presentation_time_stamp the time in milliseconds to set.
	 */
	protected void setPts(long presentation_time_stamp) {
		pts = presentation_time_stamp;
	}

	/**
	 * Sets the amount of frames per second.
	 * @param _picRate the amount of frames per second to set.
	 */
	protected void setPicRate(int _picRate) {
		picRate = _picRate;
	}

	/**
	 * Gets the amount of frames per second value.
	 * @return the amount of frames per second value.
	 */
	protected int getPicRate() {
		return picRate;
	}

	/**
	 * The method "Make_Picture" is called every time the scanner has decoded a frame.
	 * It expects the frame information as YUV values and translates them into the
	 * Java RGB color system.
	 * @param Pixels the array of pixels as YUV values.
	 */
	// 4/3/01: Unrolled inner loop -- shaved 5%
	public void Make_Picture(int Pixels[][] /*,  int f_idx, int f_type*/
	) {
		int cr, cb, blue, red, green, crb_g, luminance;

		//  because one crominance information is applied to 4 luminace values
		//  2 "pointers" are established, which point to the 2 lines containing
		//  the appropriate luminace values:
		//

		int lum_idx1 = 0, lum_idx2 = w;
		int size = (w * h) >>> 2;

		// Save time looking up array indices
		int P0[] = Pixels[0]; // Y
		int P1[] = Pixels[1]; // U
		int P2[] = Pixels[2]; // V

		/* 
		 * in-place conversion [Y', Cb, Cr] --> [R', G', B']
		 *
		 */
		for (int i = 0; i < size; i++) { // for all crominance values ...
			cb = P2[i] - 32768; // extract the
			cr = P1[i] - 32768; // chrominace information

			crb_g = (-cr * CR_DIFF_FAC - cb * CB_DIFF_FAC) >> 12;
			cr = (cr * CR_FAC) >> 12;
			cb = (cb * CB_FAC) >> 12;

			luminance = ((P0[lum_idx1] - 4096) * CY_FAC) >> 12; // extract lum.

			red = (luminance + cr) >> 8;
			green = (luminance + crb_g) >> 8;
			blue = (luminance + cb) >> 8;

			if (red < 256)
				red = (~red >> 31) & red;
			else
				red = 255;
			if (green < 256)
				green = (~green >> 31) & green;
			else
				green = 255;
			if (blue < 256)
				blue = (~blue >> 31) & blue;
			else
				blue = 255;

			Pix_Map[lum_idx1++] = (red << 16 | green << 8 | blue);

			luminance = ((P0[lum_idx1] - 4096) * CY_FAC) >> 12; // extract lum.

			red = (luminance + cr) >> 8;
			green = (luminance + crb_g) >> 8;
			blue = (luminance + cb) >> 8;

			if (red < 256)
				red = (~red >> 31) & red;
			else
				red = 255;
			if (green < 256)
				green = (~green >> 31) & green;
			else
				green = 255;
			if (blue < 256)
				blue = (~blue >> 31) & blue;
			else
				blue = 255;

			Pix_Map[lum_idx1++] = (red << 16 | green << 8 | blue);

			luminance = ((P0[lum_idx2] - 4096) * CY_FAC) >> 12; // extract lum.

			red = (luminance + cr) >> 8;
			green = (luminance + crb_g) >> 8;
			blue = (luminance + cb) >> 8;

			if (red < 256)
				red = (~red >> 31) & red;
			else
				red = 255;
			if (green < 256)
				green = (~green >> 31) & green;
			else
				green = 255;
			if (blue < 256)
				blue = (~blue >> 31) & blue;
			else
				blue = 255;

			Pix_Map[lum_idx2++] = (red << 16 | green << 8 | blue);

			luminance = ((P0[lum_idx2] - 4096) * CY_FAC) >> 12; // extract lum.

			red = (luminance + cr) >> 8;
			green = (luminance + crb_g) >> 8;
			blue = (luminance + cb) >> 8;

			if (red < 256)
				red = (~red >> 31) & red;
			else
				red = 255;
			if (green < 256)
				green = (~green >> 31) & green;
			else
				green = 255;
			if (blue < 256)
				blue = (~blue >> 31) & blue;
			else
				blue = 255;

			Pix_Map[lum_idx2++] = (red << 16 | green << 8 | blue);
			if (lum_idx1 % w == 0) { // end of line ?
				lum_idx1 += w;
				lum_idx2 += w;
			}
		}

		if (scaled_x || scaled_y) {

			int current_x = 0, current_y = 0;
			int a_index_o = 0, a_index = 0, j = 0, i = 0;
			
			try {

			if (scaled_x && (sc_x > w)) {
				for (j = h - 1; j >= 0; j--) {
					current_x = (((j + 1) * w - 1) << 8) + (1 << 7) + step_x;
					int destination_index = (j + 1) * pixel_map_width - 1;
					int eight_pixels_loops = sc_x >> 3;
					for(i = 0; i < eight_pixels_loops; i++) {
						Pix_Map[destination_index--] = Pix_Map[(current_x-=step_x) >> 8];
						Pix_Map[destination_index--] = Pix_Map[(current_x-=step_x) >> 8];
						Pix_Map[destination_index--] = Pix_Map[(current_x-=step_x) >> 8];
						Pix_Map[destination_index--] = Pix_Map[(current_x-=step_x) >> 8];
						Pix_Map[destination_index--] = Pix_Map[(current_x-=step_x) >> 8];
						Pix_Map[destination_index--] = Pix_Map[(current_x-=step_x) >> 8];
						Pix_Map[destination_index--] = Pix_Map[(current_x-=step_x) >> 8];
						Pix_Map[destination_index--] = Pix_Map[(current_x-=step_x) >> 8];
					}
					for(i = 0; i < (sc_x & 7); i++) {
						Pix_Map[destination_index--] = Pix_Map[(current_x-=step_x) >> 8];
					}
				}
			}

			if (scaled_y) {
				if(sc_y > h) {
					current_y = (((h - 1) << 8) + (1 << 7));
					for (j = sc_y - 1; j >= 0; j--) {
						System.arraycopy(
							Pix_Map,
							((int) (current_y >> 8)) * pixel_map_width,
							Pix_Map,
							j * pixel_map_width,
							pixel_map_width);
						current_y = current_y - step_y;
					}
				} else if(sc_y < h) {
					current_y = 1 << 7;
					for (j = 0; j < sc_y; j++) {
						System.arraycopy(
							Pix_Map,
							((int) (current_y >> 8)) * pixel_map_width,
							Pix_Map,
							j * pixel_map_width,
							pixel_map_width);
						current_y = current_y + step_y;
					}
				}
			}

			if (scaled_x && (sc_x < w)) {
				for (j = 0; j < sc_y; j++) {
					current_x = ((j * pixel_map_width) << 8) + (1 << 7) - step_x;
					int destination_index = j * pixel_map_width;
					int eight_pixels_loops = sc_x >> 3;
					for(i = 0; i < eight_pixels_loops; i++) {
						Pix_Map[destination_index++] = Pix_Map[(current_x+=step_x) >> 8];
						Pix_Map[destination_index++] = Pix_Map[(current_x+=step_x) >> 8];
						Pix_Map[destination_index++] = Pix_Map[(current_x+=step_x) >> 8];
						Pix_Map[destination_index++] = Pix_Map[(current_x+=step_x) >> 8];
						Pix_Map[destination_index++] = Pix_Map[(current_x+=step_x) >> 8];
						Pix_Map[destination_index++] = Pix_Map[(current_x+=step_x) >> 8];
						Pix_Map[destination_index++] = Pix_Map[(current_x+=step_x) >> 8];
						Pix_Map[destination_index++] = Pix_Map[(current_x+=step_x) >> 8];
					}
					for(i = 0; i < (sc_x & 7); i++) {
						Pix_Map[destination_index++] = Pix_Map[(current_x+=step_x) >> 8];
					}
				}
			}
			} catch (RuntimeException ex) {
				System.out.println("current_x = " + current_x);
				System.out.println("current_y = " + current_y);
				System.out.println("step_x = " + step_x);
				System.out.println("step_y = " + step_y);
				System.out.println("pixel_map_width = " + pixel_map_width);
				System.out.println("i = " + i);
				System.out.println("j = " + j);
				System.out.println("a_index = " + a_index);
				System.out.println("a_index_o = " + a_index_o);
				throw ex;
			}
		}
	}
}

/**
 * The <code>MovieScreen</code> class represents a panel on which the movie is shown.
 */
class MovieScreen extends Panel implements Runnable  {
	/** The <code>Element</code> object with the pixels of the current frame. */
	Element elem;
	/** The tool for accessing the mpeg bit stream. */
	private io_tool mpeg_stream;
	/** The movie picture. */
	public Image Picture = null;
	/** The image for double buffering. */
	public Image screen = null;
	/** The producer to produce pixels for the movie picture. */
	public MemoryImageSource source = null;
	/** The Color model for the movie picture. */
	public ColorModel colorModel = null;
	/** The current size of the scan line. */
	public int scanSize = 0;
	/** The current "buffering" percent. */
	public int currentBufferingPercent = 0;

	/** The current playing status. */
	public boolean playing = false;

	/** The code of last error. */
	public static int ErrNum = 0;
	/** The message text of last error. */
	public static String Msg = null;

	/** <tt>True</tt> if the applet is started. */
	public boolean appletStarted = false;
	/** Current image index for startup images. */
	private int preImageIndex = 0;
	/** Current image index for end images. */
	private int postImageIndex = 0;
	/** The array of startup images. */
	public Image [] preImage = null;
	/** The array of end images. */
	public Image [] postImage = null;
	/** The delay in milliseconds between startup images to show. */
	public int pre_screen_delay = -1;
	/** The delay in milliseconds between end images to show. */
	public int post_screen_delay = -1;
	/** <tt>True</tt> if startup images are looped. */
	public boolean pre_screen_loop = false;
	/** <tt>True</tt> if end images are looped. */
	public boolean post_screen_loop = false;
	/** The start time of the movie playback. */
	public long startPlayingTime = -1;
	/** The start time when the 'detecting bandwith' message is shown. */
	public long startPrintingBandwidth = -1;
	
	/** <tt>True</tt> if the startup image animation is showing. */
	private boolean pre_started = false;
	/** <tt>True</tt> if movie end image animation is showing. */
	private boolean post_started = false;

	/** "Startup" state of the player. */
	public final static int STARTUP = 1;
	/** "Played" state of the player. */
	public final static int PLAYED = 2;
	/** "Paused" state of the player. */
	public final static int PAUSED = 3;
	/** "Stopped" state of the player. */
	public final static int STOPPED = 4;
	/** "Ended" state of the player. */
	public final static int ENDED = 5;
	/** "Error" state of the player. */
	public final static int ERROR = 6;
	/** "Locked" state of the player. */
	public final static int LOCKED = 7;
	/** "Stopped" after locking state of the player. */
	public final static int L_STOPPING = 8;
	/** "Re-buffering" state of the player. */
	public final static int REBUFFERING = 9;

	/** The state of the player. */
	public int movieState = STARTUP;
	/** The start playing time. */
	public long startTime = -1;

	/** The font of the text to print messages. */
	private Font TEXT_FONT = new Font("Arial", Font.ITALIC, 10);
	/** The bold font of the text to print messages. */
	private Font TEXT_BI_FONT = new Font("Arial", Font.ITALIC | Font.BOLD, 10);
	/** Flag, eguals <tt>true</tt>, if the current os is Windows. */ 
	private boolean windows_platform = true;
	/** Flag, eguals <tt>true</tt>, if the current JRE is java2. */ 
	private boolean java2_platform = false; 

	/** The font of the text to print errors. */
	private Font ERROR_FONT =
		new Font("Times", 0, 12);

	/** <tt>True</tt> if the applet's destroy method has been called. */
	public boolean movieDestroy = false;

	/** The current playing time in milliseconds. */
	private long now = 0;
	/** The time in milliseconds of the current frame to play. */
	private long pts = 0;
	/** Presentation timestamp for last frame (reqd when estimating). */ 
	private long lastPts;

	/** The main applet's object. */
	private MPEG1 videoPlayer = null;

	/** 1/picrate, delay in milliseconds between frames. */
	private static int picLags[] =
		{ -1, // forbidden (ISO/IEC 11172-2: 1993 (E) p. 24)
		42, // 23.976
		42, // 24
		40, // 25
		33, // 29.97
		33, // 30
		20, // 50
		17, // 59.94
		17, // 60
		66, // 15
		33, // 30
		33, // 30
		33, // 30
		66, // 15
		33, // 30
		33, // 30
		-1, -1, -1, -1, -1, -1, -1 // reserved
	};

	/**
	 * Constructs a <code>MovieScreen</code> using the specified applet object.
	 * Adds mouse listener to the applet 
	 * and if necessary fixs the netscape bug under linux platform with the italic fonts.  
	 * @param videoPlayer the applet object.
	 */
	public MovieScreen(MPEG1 videoPlayer) {
		super();
		setLayout(null);
		this.videoPlayer = videoPlayer;
		this.addMouseListener(videoPlayer);
		try {
			Class.forName("java.awt.Graphics2D");
			java2_platform = true;
		} catch (Exception ex) {
		}
		windows_platform = "\\".equals(System.getProperty("file.separator"));
/*		
		// fix netscape bug under linux platform with bolditalic fonts
		if(getFontMetrics(TEXT_FONT).stringWidth("detecting bandwidth...") < 32)
		    TEXT_FONT = new Font("Arial", 0, 10);
*/		    
	}

	/**
	 * Inits this <code>MovieScreen</code> object with the specified tool for accessing the mpeg bit stream. 
	 * @param _mpeg_stream the tool for accessing the mpeg bit stream.
	 */
	public void init(io_tool _mpeg_stream) {
		lastPts = 0;
		mpeg_stream = _mpeg_stream;
	}

	/**
	 * Sets the <code>Element</code> object with the pixels of the current frame. 
	 * @param _elem the <code>Element</code> object with the pixels of the current frame.
	 */
	public void setElement(Element _elem) {
		elem = _elem;
	}

	/**
	 * Calculates amount of intra-frame delay from current picRate.
	 * This value is used only when an explicit PTS value is not
	 * available.
	 * @return the amount of intra-frame delay from current picRate.
	 */
	private int getLag() {
		int ret = picLags[elem.getPicRate()];
		if (ret == -1) {
			// This only happens on non-standard framerates.
			// We'll just hack it for now.
			// FIXME: return difference between last two valid PTS values.
			return 42;
		}
		return ret;
	}

	/** The graphics context to paint the movie screen. */
	private Graphics graphics = null;

	/**
	 * Stub, overwrites the "update()" method of Panel (does nothing).
	 * @param g the graphics context to paint.
	 */
	public void update(Graphics g) {
	}

	/**
	 * Draws the movie screen.
	 * Gets the graphics context and calls the method to draw the screen on it.  
	 */
	public void myPaint() {
		graphics = graphics == null ? getGraphics() : graphics;
		if (graphics != null)
			paint(graphics);
	}

	/**
	 * Prints the string on the screen.
	 * @param s the string to print.
	 * @param x the x coordinate of the string's first character (left baseline point).
	 * @param y the y coordinate of the string's first character (left baseline point).
	 */
	public void write(String s, int x, int y) {
		if (graphics == null)
			graphics = getGraphics();
		graphics.setColor(Color.white);
		graphics.drawString(s, x, y);
	}

	/**
	 * Redraws the movie screen. 
	 */
	public void repaint() {
		myPaint();
	}

	/**
	 * Paints the controls and applet's detecting bandwidth/buffering text on the screen's graphics context 
	 * and calls to the method that paints the movie's frame and prints other text/error messages.
	 * @param g the graphics context to paint.
	 */
	public void paint(Graphics g) {
		Graphics newGr = g;
		try {
			if (screen != null)
				newGr = screen.getGraphics();
		} catch (Exception ex) {}

		paintScreen(newGr);
		if((movieState != LOCKED) && (movieState != L_STOPPING) && 
			(videoPlayer.controlPanel != null)
				&& (videoPlayer.show_controls || !videoPlayer.float_controls)) {
				videoPlayer.controlPanel.paint(newGr);
		}

		long now = new Date().getTime();
		
		if (videoPlayer.feedback_agent && (movieState == STARTUP)) {
			if(windows_platform && java2_platform) {
				newGr.setFont(TEXT_BI_FONT);
			} else {
				newGr.setFont(TEXT_FONT);
			}
			newGr.setColor(Color.white);
			if(videoPlayer.detected_speed > 0
			&& (startPrintingBandwidth == -1 || ((now - startPrintingBandwidth) < 2000))) {				
				if(startPrintingBandwidth == -1)
					startPrintingBandwidth = now;										
				if((now - startPrintingBandwidth) < 1000) {
					newGr.drawString(
						"detecting bandwidth...",
						getSize().width - ((windows_platform && java2_platform) ? 135 : 130),
						getSize().height - 11);
				} else {							
					String cs = videoPlayer.file_speeds[videoPlayer.detected_speed];
					int iCs = cs.indexOf('_');
					if(iCs != -1) {
						cs = cs.substring(0, iCs - 1);
					}
					newGr.drawString(
						cs + " kbps version chosen",
						getSize().width - ((windows_platform && java2_platform) ? 135 : 130),
						getSize().height - 11);
				}			
			} else if((videoPlayer.buffered == false) && (videoPlayer.detected_speed != -1)) { 
				newGr.drawString(
					"buffering... " + currentBufferingPercent + "%",
					getSize().width - 85,
					getSize().height - 11
				);
			}
		}

		if (newGr != g)
			g.drawImage(screen, 0, 0, this);
	}

	/**
	 * Paints the movie's frame on the screen's graphics context 
	 * and prints text/error messages (f.e. rebuffering information or MediaFrame locked error). 
	 * @param g the graphics context to paint.
	 */
	private void paintScreen(Graphics g) {
		if (g == null || movieDestroy)
			return;

		int w = getSize().width, h = getSize().height;

		if (MovieScreen.Msg != null) {
			movieState = ERROR;
			g.setColor(Color.black);
			g.fillRect(0, 0, w, h);
			g.setFont(ERROR_FONT);
			g.setColor(Color.white);
			g.drawString(MovieScreen.Msg, 10, 20);
			return;
		}

		if (mpeg_stream != null) {
			if (mpeg_stream.is_eof())
				movieState = ENDED;
		}
		
		if (pre_started && movieState != STARTUP) {
			preImageIndex = 0;
			pre_started = false;
		}

		if (post_started && movieState != ENDED) {
			postImageIndex = 0;
			post_started = false;
		}

		if (movieState == STARTUP) {
			Image pre_image = videoPlayer.show_mf_logo ? videoPlayer.mf_logo : preImage[preImageIndex]; 
			if(pre_image != null) {
				g.drawImage(pre_image, 0, 0, w, h, this);
			}
			if(! pre_started && (((preImage.length > 1) && (pre_screen_delay > 0)) || videoPlayer.show_mf_logo)) {
				pre_started = true;
				start();
			}
			return;

		} else if (mpeg_stream == null || elem == null) {

			movieState = ERROR;
			g.setColor(Color.black);
			g.fillRect(0, 0, getSize().width, getSize().height);
			g.setColor(Color.white);
			g.setFont(ERROR_FONT);
			g.drawString("SEARCHING FIRST I-FRAME...", 10, 20);
			if (getParent() instanceof MPEG1) {
				Player p = ((MPEG1) getParent()).getAudioPlayer();
				if (p == null)
					g.drawString("NO AUDIO", 10, 40);
				else if (p.isStopped())
					g.drawString("NO AUDIO POSSIBLE", 10, 40);
				else
					g.drawString("TRYING AUDIO", 10, 40);
			}
			return;
		}

		if (movieState == ENDED && postImage[postImageIndex] != null) {
			g.drawImage(postImage[postImageIndex], 0, 0, w, h, this);
			if(! post_started && postImage.length > 1 && post_screen_delay > 0) {
				post_started = true;
				start();
			}
			return;
		}

		if(movieState == LOCKED || movieState == L_STOPPING ||
			(videoPlayer.isLicenseVerified() && videoPlayer.isCorrectLicense() == false
			&& startPlayingTime != -1 
			&& ((new Date().getTime() - startPlayingTime) > 5000))) {
			g.setColor(Color.black);
			g.fillRect(0, 0, getSize().width, getSize().height);
			g.setColor(Color.white);
			g.setFont(ERROR_FONT);
			g.drawString("MEDIAFRAME LOCKED!", getSize().width/2 - 70, getSize().height/2 - 5);
			if(movieState != LOCKED && movieState != L_STOPPING) {
				movieState = L_STOPPING;
				MovieScreen.ErrNum = 10;
				videoPlayer.playerStop();
			}
			return;
		} else {
			source.newPixels(elem.Pix_Map, colorModel, 0, scanSize);
			g.drawImage(Picture, 0, 0, this);
		}

		if(videoPlayer.feedback_agent) {
			if(! java2_platform) {
				g.setFont(TEXT_FONT);
			} else {
				g.setFont(TEXT_BI_FONT);
			}
			g.setColor(Color.white);
			if(movieState == REBUFFERING) {
				g.drawString(
					"re-buffering... " + currentBufferingPercent + "%",
					getSize().width - (java2_platform ? 120 : 98),
					getSize().height - 11
				);
				return;
			}

			if (videoPlayer.feedback_agent && startTime > 0) {
				if ((new Date().getTime() - startTime) < 1000) {
					w = getSize().width - 76;
					h = getSize().height - 11;
					if (movieState == PAUSED) {
						g.drawString("paused", w, h);
					} else if (movieState == PLAYED) {
						g.drawString("playing...", w, h);
					}
				} else {
					startTime = 0;
				}
			}
		}
	}

	/**
	 * Pauses the movie playback or calibrates the playing time 
	 * depending from the state of the player (playing, pausing, etc), 
	 * the current playing time and the current time of the frame. 
	 * @return true, if the frame should be painted.
	 * @exception InterruptedException raises if another process interrupts the current process.
	 */
	public boolean nextFrame() throws InterruptedException {

		if (mpeg_stream == null)
			return false;

		pts = elem.getPts();

		//		System.out.println(pts);

		// If we cannot get pts value -> make delay frame-rate dependant
		if (pts == -1)
			pts = lastPts + getLag();

		//g.drawString(""+cat, 100, 70);

		// If pts looped, reset _start_time
		if (pts < lastPts - 1000) {
			mpeg_stream.getBuffer().setStartTime(
				System.currentTimeMillis() - pts);
			lastPts = pts - getLag();
		}

		// If the pts didn't change -> make delay frame-rate dependant
		else if (pts <= lastPts)
			pts = lastPts + getLag();

		now = mpeg_stream.getBuffer().getCurrentTime();
		/*
		                cat = videoPlayer.getAudioPlayingTime();
		                long a_v = now - cat;
				
		//		System.out.println("a_v: " + a_v + " cat: " + cat + " now: " + now + " pts: " + pts);
		
		                // If no audio or weird audio times (with looping)
		//                if (cat == -1 || a_v < -10000 || a_v > 10000)
		//                {
		                          cat = now;
		                          a_v = 0;
		//                }
		
		                // audio-sync (allow 100 ms difference)
		                if (a_v < -100 || a_v > 100)
		                {
		                        mpeg_stream.getBuffer().addToStartTime(a_v);
		                        now = mpeg_stream.getBuffer().getCurrentTime();
		               }
		
		                System.out.print("\r-- now: "+now+" pts: "+pts+" cat: "+cat+
		                        " a-v: "+a_v+" start: "+DataBuffer.getStartTime());
		                System.out.flush();
		
		*/

		lastPts = pts;

		int sleepieTime = (int) ((pts - now));
		if (now == 0)
			sleepieTime = 0;

		// On my machine, a call to sleep with any value > 0 results in a minimum
		// 10 msec delay.  Hence, only sleep if lag > 10 ms

		// -- if for some reason it wants to wait for 5 sec... don't...

		if (sleepieTime > 10 && sleepieTime < 5000) {
			Thread.sleep(sleepieTime - 10);
			//long now2 = DataBuffer.getCurrentTime();
		}

		if (!playing) {
			long n = System.currentTimeMillis();
			long oldStartPlayingTime = startPlayingTime;
			while (!playing) {
				Thread.sleep(100);
				if (movieState == PAUSED) {
					if(startPlayingTime != -1 && oldStartPlayingTime != 1)
						startPlayingTime = oldStartPlayingTime + (System.currentTimeMillis() - n);
					myPaint();
					// This code looks not fun, however it works.
					if(startPlayingTime != -1 && oldStartPlayingTime != 1)
						startPlayingTime = oldStartPlayingTime + (System.currentTimeMillis() - n);
				}
			}
			long wait = System.currentTimeMillis() - n;
			mpeg_stream.getBuffer().addToStartTime(wait);
		}
		return true;
	}

	/**
	 * Starts playing startup or end image animation of the applet.
	 */
	public void start() {
		screen_thread = new Thread(this, "Screen Thread");
		screen_thread.start();
	}

	/**
	 * The main thread's method (enter point) that plays startup or end image animation of the applet.
	 */
	public void run() {
		int state = movieState;
		if(state != ENDED && state != STARTUP)
			return;

		int count = 0;
		while (!appletStarted && count ++ < 100) {
			try {
				Thread.sleep(100);
			} catch (Exception ex) {}
		}
			
		int delay = pre_screen_delay;

		if(state == ENDED)
			delay = post_screen_delay;

		Thread thisThread = Thread.currentThread();
		while(thisThread == screen_thread && movieState == state) {
			try {
				Thread.sleep(videoPlayer.show_mf_logo ? 3000 : delay);
			} catch (Exception ex) {}
			
			if((thisThread == screen_thread) && (movieState == state) && ! videoPlayer.show_mf_logo) {
				if(state == STARTUP) {
					preImageIndex ++;
					if(preImageIndex >= preImage.length) {
						if(! pre_screen_loop) {
							preImageIndex --;
							screen_thread = null;
						} else
							preImageIndex = 0;
					}
				} else {
					postImageIndex ++;
					if(postImageIndex >= postImage.length) {
						if(! post_screen_loop) {
							postImageIndex --;
							screen_thread = null;
						} else
							postImageIndex = 0;
					}
				}
			}
			if(videoPlayer.show_mf_logo) {
				preImageIndex = 0;
				videoPlayer.show_mf_logo = false;
			}
			if(thisThread == screen_thread && movieState == state)
				myPaint();
		}
	}

	/** The thread for playing startup or end image animation. */
	private volatile Thread screen_thread; 

	/**
	 * Stops playing startup or end image animation of the applet.
	 */
	public void stop() {
		screen_thread = null;		
	}

}

/**
 * The class "MPEG1" is the main class of the MPEG1 Applet (Java MPEG inline player).
 */
@AppletServerResourcePath("mediaframe/mpeg1/")
public class MPEG1
	extends JApplet
	implements MouseListener, ActionListener {
	/** The height in MPEG stream. */
	private int h;
	/** The width in MPEG stream. */
	private int w; 
	/** The original height in MPEG stream. */
	private int o_h; 
	/** The original width in MPEG stream. */
	private int o_w;
	/** <tt>True</tt> if the applet starts playback. */ 
	private boolean firstAppletLoop = true;
	/** The name of the video file. */
	private String filename;
	/** The length of the video file. */ 
	private int video_length = -1;
//	/** The frame (in application). */
//	private Frame the_frame;

	/** Last P or I frame. */
	private int last_P_or_I = 0; 
	/** The RGB color model for the movie screen. */
	private DirectColorModel cm =
		new DirectColorModel(24, 0xff0000, 0xff00, 0xff);

	/** The last frame in "EL_ARR[]". */
	private int Frame_nr = 0;
	/** Number of "Element"s in "EL_ARR[]". */
	private final int MAXELEMENT = 5;
	/** The array of <code>Elements</code> (objects that contain movie frames). */
	private Element EL_ARR[] = new Element[MAXELEMENT];
	/** <tt>True</tt> if the applet's state is PLAYED. */
	private boolean playing = true;
	/** The mute sound state of the player (false if sound is on). */
	private boolean mute;
	/** The 'MediaFrame' &trade; logo. */ 
	Image mf_logo = null;
	/** Flag, equals <tt>true</tt> if 'MediaFrame' &trade; logo is showing. */ 
	volatile boolean show_mf_logo = false;

	/** The popup menu of the applet. */
	PopupMenu popup = new PopupMenu();
	/** The "ID" popup menu item. */
	private MenuItem idItem = new MenuItem();
	/** The "Play" popup menu item. */
	private MenuItem playItem = new MenuItem("Play");
	/** The "mute" popup menu item. */
	private MenuItem muteItem = new MenuItem("mute");
	/** The "Save video as..." popup menu item. */
	private MenuItem saveItem = new MenuItem("Save video as...");
	/** The control panel of the applet. */
	public IControlPanel controlPanel;

	// Applet parameters
	/** <tt>True</tt> if the applet should autoplay the movie. */
	private boolean autoPlay = false;
	/** <tt>True</tt> if the applet should play the movie after clicking on the movie screen. */
	private boolean clickToPlay = false;
	/** <tt>True</tt> if the applet should play the movie after rolloving above the movie screen. */
	private boolean rolloverToPlay = false;
	/** <tt>True</tt> if the applet should show the feedback information (speed detection, (re-)buffering). */
	public  boolean feedback_agent = true;
	/** The value of the "target" applet's parameter. */
	private String target;
	/** The value of the "address" applet's parameter. */
	private String address;
	/** The value of the "preScreen" applet's parameter. */
	private String preScreen = "posters/pre.gif";
	/** The value of the "postScreen" applet's parameter. */
	private String postScreen = "posters/post.gif";
	/** <tt>True</tt> if applet's control panel should be showed. */
	public boolean player_controls = true;
	/** <tt>True</tt> if applet's control panel should be showed only after rolloving above the movie screen. */
	public boolean float_controls = false;
	/** <tt>True</tt> if applet's control panel is showed (has the meaning only if float_controls = true). */
	public boolean show_controls = false;
	/** <tt>True</tt>, if the current os is Linux. */ 
	private boolean linux_platform = false;	
//	private boolean selfWindow = false;
	/** The width of the applet's movie screen. */
	private int window_x;
	/** The height of the applet's movie screen. */
	private int window_y;
	/** The value of the current volume. */
	private float current_volume = 1.f;

	/** 
	 * <tt>True</tt> if the applet should load the movie into the buffer, 
	 * false if the movie already in the buffer. 
	 */
	private boolean firstLoop = true;

	/** <tt>True</tt> if the applet should restart the playing after the playback is finished. */
	public boolean playbackLoop = false;

	/** <tt>True</tt> if the applet is rewinding the movie (tries to restart the playback from the begin after it is finished). */
	private boolean rewinded = false;
	/** <tt>True</tt> if the applet is ready to play the movie's first frame. */
	private boolean readyToPlay = false;
	
	/** The state of the audio parameter of the applet */
	private boolean audioEnabled = true;

	/** The file name of the movie which is used in the mediaframe_load javascript function */
	private String new_filename = null;

	/** <tt>True</tt> if the player is used with the correct license key. */
	private boolean correctLicense = false;
	/** <tt>True</tt> if the license is already verified. */
	private boolean licenseVerified = false;
	
	/** <tt>True</tt> if the license is stored in the video file. */
	private boolean video_license = false;
	/** The URL address of the movie file. */
	private URL source_url = null; 
	
	/** The name of the script for the 'Save video as...' functionality */
	private String saveScript = "movie_save.php";
	/** <tt>True</tt> if the 'Save video as...' functionality is enabled */
	private boolean saveVideoEnabled = true;
	
	/** The stream with only video-frames. */
	private io_tool mpeg_stream; 
	/** The stream with only audio-frames. */
	private io_tool audio_stream;
	/** The reader of the movie stream, contains both streams audio and video. */
	private MPEG_Reader input_reader;
	/** The video decoder. */
	private MPEG_video video_decoder; 
	/** The audio player/decoder. */	
	private Player audio_decoder;
	/** The location of the directory where the xx.detect.speed files are placed. */ 
	private String detect_location;

	/** The panel on which the movie is shown. */
	public MovieScreen movieScreen = new MovieScreen(this);
	/** The url of a web page with the description of the MEDIAFRAME LOCKED error. */
	private String lockedInfo = "http://mediaframe.org/locked.html";

	/** 
	 * Current internet connection bandwidth value (index for tables).
	 * @see #file_speeds
	 * @see #integer_speeds
	 */
	public int detected_speed = -1;
	
	/** Parameter names for the movie files that depend from the bandwidth value. */
	public String file_speeds[] = {
		"default_media", 
		"24k_media", 
		"44k_media", 
		"128k_media", 
		"256k_media", 
		"350k_media", 
		"500k_media",
		"800+_media"
	};
	
	/** Possible bandwidth values supported by the applet. */
	public final int integer_speeds[] = {-1, 24, 44, 128, 256, 350, 500, 800};

	/**
	* Detects the speed of the internet connection.
	* @return the name of applet's parameter with a movie file for the detected bandwidth value.
	* @throws Exception raises if an error occurs.
	*/
	private final String detectConnectionSpeed() {
		
		try {
			int detect_size = 8;
			int fileLength = 0;
			long download_time = 0;
			byte[] buf = new byte[100]; 

			for(int downloads = 0; downloads < 2; downloads ++) {
				URLConnection uc = new URL(getCodeBase(), detect_location + detect_size + ".detect.speed?detect=true").openConnection();
				uc.setUseCaches(false);
				uc.connect();
				long start_download_time = new Date().getTime();
				fileLength = uc.getContentLength() + 332;  // calculates the length of the file + headers
				if(fileLength < 4428) {
					return file_speeds[detected_speed = 0];
				}
				long is_open_time = new Date().getTime() - start_download_time;
				start_download_time += is_open_time;
				InputStream is = uc.getInputStream();
				int readed = 0;
				while(is.read(buf) != -1) {
				}
				download_time = new Date().getTime() - start_download_time;
				download_time += is_open_time;
				is.close();
				if((downloads == 0) && (download_time < 2000)) {
					while((download_time < 2000) && (detect_size < 256)) {				
						detect_size *= 2;
						download_time *= 2;
					}
				} else {
					break;
				}
			}
			download_time = (download_time * 4096) / fileLength;			

			if(download_time >= 1100) {		  // 1333 - 121		//  fixed to expirimental 1100 value (~28000)
				return file_speeds[detected_speed = 1];
			} else if(download_time >= 632) { // 727 - 95
				return file_speeds[detected_speed = 2];
			} else if(download_time >= 225) { // 250 - 25
				return file_speeds[detected_speed = 3];
			} else if(download_time >= 115) { // 125 - 10		// fixed to experimental 115
				return file_speeds[detected_speed = 4];
			} else if(download_time >= 77) {  // 91 - 6 = 85	// fixed to experimental 77  
				return file_speeds[detected_speed = 5];
			} else if(download_time >= 57) {  // 64 - 7
				return file_speeds[detected_speed = 6];
			}
			return file_speeds[detected_speed = 7];	  // 40
		} catch (Exception ex) {
		}
		return file_speeds[detected_speed = 0];		
	}


	/**
	 * Constructs the applet's object.
	 */
	public MPEG1() {

		super();

		MenuItem about = new MenuItem("About MediaFrame");
		MenuItem copyright = new MenuItem("Copyright information");

		playItem.setEnabled(false);
		playItem.addActionListener(this);
		muteItem.addActionListener(this);
		saveItem.addActionListener(this);
		
		about.addActionListener(this);
		copyright.addActionListener(this);

		popup.add(idItem);
		popup.addSeparator();
		popup.add(playItem);
		popup.add(muteItem);
		popup.addSeparator();
		popup.add(saveItem);
		popup.addSeparator();
		popup.add(about);
		popup.add(copyright);

		movieScreen.add(popup);
	}


	/**
	 * This method is invoked when user selects the item of applet's popup menu. 
	 * @param event event's data.
	 */
	public void actionPerformed(ActionEvent event) {
		String s = event.getActionCommand();
		if (s.equals("Play")) {
			if (!playing)
				if (movieScreen.movieState != MovieScreen.STOPPED
					&& movieScreen.movieState != MovieScreen.STARTUP) {
					playbackLoop = false;
					playerrewind();
					wait_rewind();
				}
			playerplay();
		} else if (s.equals("Pause")) {
			playerpause();
		} else if (s.equals("About MediaFrame")) {
			address = "http://mediaframe.org";
			openTarget();
		} else if (s.equals("Save video as...") && source_url != null) {
			try {
				String moviePath = source_url.toString().substring(new String(source_url.getProtocol() + "://" + source_url.getHost()).length());
				if(saveScript.indexOf("http://") > 0)
					getAppletContext().showDocument(new URL(saveScript + "?file=" + moviePath));
				else
					getAppletContext().showDocument(new URL(getCodeBase(), saveScript + "?file=" + moviePath));
			} catch (java.net.MalformedURLException MUE) {
				System.out.println("MPEG1.mouseClicked: " + MUE);
			}
		} else if (s.equals("Copyright information")) {
			address = "http://copyright.mediaframe.org";
			openTarget();
		} else if (s.equals("un-mute") || s.equals("mute")) {
			playersetmute(!mute);
		}
	}

	/**
	 * Opens a movie file or a zip archive with a movie file for downloading and buffering.
	 * If necessary detects the internet connection bandwidth and chooses the right movie file.
	 * @return <tt>true</tt> if the file has been opened successfully.
	 */
	private boolean open_url() {
		boolean io = true; // everything OK ????
		// to build the URL to MPEG resource
		String f_name = null;

		if (firstLoop) { // otherwise the file is already opened
			try { // obtain parameters
				if(new_filename != null) {
					f_name = new_filename;
					new_filename = null;
				} else {
					if ((detected_speed == -1) && (getParameter(file_speeds[1]) != null)) {
						detectConnectionSpeed();
					} else if(detected_speed == -1) {
						detected_speed = 0;
					} 
					f_name = getParameter(file_speeds[detected_speed]);
					if ((filename != null)	&& (f_name != null)
							&& filename.equals(f_name) && input_reader != null)
							return io;
				}

				filename = f_name;

				InputStream is = null;
				boolean no_cache = "false".equalsIgnoreCase(getParameter("cache"));
				boolean try_zip = false;
				if (f_name.toLowerCase().endsWith(".zip")) //if zip
					try_zip = true;

				// First try to open relative URL
				try {
					source_url = new URL(getCodeBase(), f_name);
					URLConnection conn = source_url.openConnection();
					if(no_cache)
						conn.setUseCaches(false);
					if (try_zip)
						is = getZipStream(conn.getInputStream());
					else {
						video_length = conn.getContentLength();
						is = conn.getInputStream();
					}
				} catch (Exception e1) {
					// Then try to open absolute URL
					try {
						source_url = new URL(f_name);
						URLConnection conn = source_url.openConnection();
						if(no_cache)
							conn.setUseCaches(false);
						if (try_zip)
							is = getZipStream(conn.getInputStream());
						else {
							video_length = conn.getContentLength();
							is = conn.getInputStream();
						}
					} catch (Exception e2) {
						// And after that try to open it af a file
						try {
							ZipFile zf = new ZipFile(f_name);
							Enumeration en = zf.entries();
							if (!en.hasMoreElements())
								throw new IOException("Cannot find a movie file in a zip");
							ZipEntry ze = (ZipEntry) en.nextElement();
							is = zf.getInputStream(ze);
						} catch (ZipException ze) { //not zip file
						}
						if (is == null)
							is = new FileInputStream(f_name);
					}
				}
				
				int licenseBufferLen = 0; 
				// try to load the license key from the movie file and verify it				
				if(video_license) {
					correctLicense = false;
					try {
						Class[] param_classes = new Class[1];
						param_classes[0] = InputStream.class;
						Object[] params = new Object[1];
						params[0] = is;
						String license_key = (String)Class.forName("mediaframe.mpeg4.LicenseManager").getMethod(
							"loadVideoLicense", param_classes).invoke(null, params);
						if(license_key.length() > 2) {
							param_classes = new Class[2];
							param_classes[0] = URL.class;
							param_classes[1] = String.class;
							params = new Object[2];
							params[0] = getCodeBase();
							params[1] = license_key.substring(2);
							correctLicense = ((Boolean)Class.forName("mediaframe.mpeg4.LicenseManager").getMethod(
								"isCorrectLicenseKey", param_classes).invoke(null, params)).booleanValue();
							is = new CryptedInputStream(is);							
							licenseVerified = true;
						} else {
							throw new Exception("The license does not exist in the movie file");
						}
					} catch (InvocationTargetException ex) {
						throw ex.getTargetException();
					} catch (ClassNotFoundException clEx) {
						video_license = false;
						correctLicense = licenseVerified = true;
					}
				}

				int maxBufferSize = 0;
				String bufferSize = getParameter("pre_buffer");
				
				if (bufferSize != null)
					if(bufferSize.charAt(bufferSize.length() - 1) == '%') {
						try {
							maxBufferSize = video_length * Integer.parseInt(bufferSize.substring(0, bufferSize.length() - 1)) / 100;
						} catch (Exception ex) {}
					} else {				
						try {
							maxBufferSize = Integer.parseInt(bufferSize);
						} catch (Exception ex) {}
					}
					
				int minBufferSize = (maxBufferSize * 3) / 5; // = 60% from maxBufferSize

				input_reader = new MPEG_Reader(this, is, maxBufferSize, minBufferSize, video_length - licenseBufferLen);

			} catch (NullPointerException e) {
				MovieScreen.ErrNum = 2;
				MovieScreen.Msg = "no parameter \"xxx_media\"";
				io = false;
			} catch (Throwable e) {
				MovieScreen.ErrNum = 1;
				MovieScreen.Msg = "ERROR: " + e.getMessage();
				io = false;
			}
			if (f_name == null) {
				MovieScreen.ErrNum = 2;
				MovieScreen.Msg = "no parameter \"xxx_media\"";
				io = false;
			}
			firstLoop = false;
		}
		return io;
	}

	/**
	 * Inits the applet.
	 * Gets all values of applet's parameters, inits applet's control panel and downloads startup and end images.
	 */	
	public void init() {

		System.out.println("MPEG1::init()");

			window_x = getSize().width;
			window_y = getSize().height;

			setBackground(Color.white);

			// Process audio parameter
			String audio = getParameter("audio");
			if (audio == null || audio.equalsIgnoreCase("yes") 
			|| 	audio.equalsIgnoreCase("true")) 
				audioEnabled = true;
			else
				audioEnabled = false;

			mute = ! isSoundEnabled();
			setMuteItemState(mute);
			
			target = getParameter("target"); // URL target
			address = getParameter("address"); // URL address
			
			// Process parameters for 'Save video as...' functionality
			if(getParameter("save_script") != null)
				saveScript = getParameter("save_script");
			 
			if(getParameter("allow_save") != null)
				saveVideoEnabled = "true".equalsIgnoreCase(getParameter("allow_save")) ||
								"yes".equalsIgnoreCase(getParameter("allow_save"));

			// Process the parameter "playback"
			autoPlay = "auto_start".equalsIgnoreCase(getParameter("playback"));
			clickToPlay = "click_to_play".equalsIgnoreCase(getParameter("playback"));
			rolloverToPlay = "rollover_to_play".equalsIgnoreCase(getParameter("playback"));

			playbackLoop = "true".equalsIgnoreCase(getParameter("loop")) ||
							"yes".equalsIgnoreCase(getParameter("loop"));
			
			feedback_agent = "true".equalsIgnoreCase(getParameter("feedback_agent")) ||
							  "yes".equalsIgnoreCase(getParameter("feedback_agent"));
							  

			video_license = "true".equalsIgnoreCase(getParameter("video_license")) ||
						     "yes".equalsIgnoreCase(getParameter("video_license"));
						     
			if(video_license || saveVideoEnabled == false)
				saveItem.setEnabled(false);

			idItem.setLabel(getParameter("id"));

			preScreen = getParameter("pre_screen");
			postScreen = getParameter("post_screen");
			current_volume = parseVolume(getParameter("startup_volume"));
			
			String preScreenRange = getParameter("pre_screen_range");
			String postScreenRange = getParameter("post_screen_range");
			
			movieScreen.preImage = getImageFiles(preScreen, getParameter("pre_screen_range"));
			movieScreen.postImage = getImageFiles(postScreen, getParameter("post_screen_range"));

			detect_location = getParameter("detect_location");
			if(detect_location == null) {
				detect_location = "detect_speed/"; 
			} else if(detect_location.length() > 0) {
				char lastChar = detect_location.charAt(detect_location.length() - 1);
				if(lastChar != '/') {
					detect_location = detect_location + '/';
				}
			}
			String control_location = getParameter("control_location");
			if(control_location != null) {
				if(control_location.charAt(control_location.length() - 1) != '/')
					control_location += '/';
				mediaframe.mpeg1.controlpanel.ControlButton.IMAGE_PATH = control_location;
			}

			movieScreen.pre_screen_loop = 
				"true".equalsIgnoreCase(getParameter("pre_screen_loop")) ||
				 "yes".equalsIgnoreCase(getParameter("pre_screen_loop"));

			movieScreen.post_screen_loop = 
				"true".equalsIgnoreCase(getParameter("post_screen_loop")) ||
				 "yes".equalsIgnoreCase(getParameter("post_screen_loop"));
				 
		

			try {
				movieScreen.pre_screen_delay = Integer.parseInt(getParameter("pre_screen_delay"));
			} catch (Exception ex) {}
			
			try {
				movieScreen.post_screen_delay = Integer.parseInt(getParameter("post_screen_delay"));
			} catch (Exception ex) {}

			this.addComponentListener(new ComponentListener() {

				public void componentHidden(ComponentEvent e) {
				}

				public void componentMoved(ComponentEvent e) {
				}

				public void componentResized(ComponentEvent e) {
					window_x = getSize().width;
					window_y = getSize().height;
					movieScreen.setBounds(0, 0, window_x, window_y);
				}

				public void componentShown(ComponentEvent e) {
				}
				
			});
					
			movieScreen.setBounds(0, 0, window_x, window_y);
			
			linux_platform = "Linux".equalsIgnoreCase(System.getProperty("os.name"));

		try {
			for(int i = 0; i < movieScreen.preImage.length; i++) {
				MediaTracker mt = new MediaTracker(this);
				mt.addImage(movieScreen.preImage [i], 0);
				mt.waitForID(0);
			}
			for(int i = 0; i < movieScreen.postImage.length; i++) {
				MediaTracker mt = new MediaTracker(this);
				mt.addImage(movieScreen.postImage [i], 0);
				mt.waitForID(0);
			}
		} catch (Exception e) {
			System.out.println(e);
		}

		player_controls =
			(float_controls =
				"float".equalsIgnoreCase(getParameter("controls")))
				|| "yes".equalsIgnoreCase(getParameter("controls"))
				|| "true".equalsIgnoreCase(getParameter("controls"));

		if (player_controls) {
			try {
				Class cls = Class.forName("mediaframe.mpeg1.controlpanel.ControlPanel");

				controlPanel = (IControlPanel) cls.newInstance();

			} catch (Exception e) {
				player_controls = false;
			}
		}

			movieScreen.setCursor(
				Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
			setLayout(null);

			if (player_controls) {
				controlPanel.initPanel(this, window_y);
			}

/*
			if (selfWindow) {
				the_frame.setLayout(new BorderLayout());
				the_frame.add(movieScreen, "Center");
				the_frame.setTitle(getParameter("id"));
				the_frame.show();
			} else {
*/				add(movieScreen);
//			}

			Image mf_image = null;
			if(mf_image != null) {
				mf_logo = createImage(window_x, window_y);
				Graphics imageGr = mf_logo.getGraphics();
				imageGr.setColor(new Color(51, 51, 51));
				imageGr.fillRect(0, 0, window_x, window_y);
				imageGr.drawImage(mf_image, (window_x - mf_image.getWidth(null))/ 2, (window_y - mf_image.getHeight(null)) / 2, null);
				show_mf_logo = true;
			}

			movieScreen.myPaint();
	}

	/**
	 * Gets the image files using the base filename and the fileRange parameters.
	 * f.e. filename = preimage.gif, fileRange = 1-4, 
	 * preimage1.gif ... preimage4.gif images should be downloaded. 
	 * If the fileRange == null or incorrect value, 
	 * the image with the fileName should be downloaded.
	 * @param fileName the base filename.
	 * @param fileRange the range of the files.
	 * @return the array of images that have been loaded.
	 */
	private Image[] getImageFiles(String fileName, String fileRange) {
			Image[] images = null;
			int iScreenRangeFirst = -1;
			int iScreenRangeLast = -1;
			try { 
				iScreenRangeFirst = Integer.parseInt(fileRange.substring(0, fileRange.indexOf('-')));
				iScreenRangeLast = Integer.parseInt(fileRange.substring(fileRange.indexOf('-') + 1));
			} catch (Exception ex) {}
			
			if(iScreenRangeFirst > 0 && iScreenRangeLast > 0) {
				int dotIndex = fileName.indexOf('.');
				images = new Image [iScreenRangeLast - iScreenRangeFirst + 1];
				for(int i = iScreenRangeFirst, j = 0; i <= iScreenRangeLast ; i++, j++) {
					String imageFileName = fileName.substring(0, dotIndex) + i + fileName.substring(dotIndex);
					images[j] = getImage(getDocumentBase(), imageFileName);
				}
			} else {
				images = new Image [1];
				images[0] = getImage(getDocumentBase(), fileName);
			}
			return images;
	}

	/**
	 * The method "start()" opens up the MPEG resource 
	 * and produces and starts the "MPEG_Reader" thread.
	 * Creates both video and audio stream, audio and video decoder.
	 */
	public void start() {
		System.out.println("MPEG1::start()");

		boolean io = true;
		MovieScreen.ErrNum = 0;
		MovieScreen.Msg = null;

		movieScreen.movieState = MovieScreen.STARTUP;

		if (io =
			open_url()) { // create the MPEG_Reader object and start the MPEG_Reader thread:

			mpeg_stream = new io_tool(io_tool.TYPE_VIDEO, input_reader.getVideoBuffer());
			video_decoder = new MPEG_video(this, mpeg_stream);

			io_tool audio_stream = new io_tool(io_tool.TYPE_AUDIO, input_reader.getAudioBuffer());
			audio_decoder = new Player(audio_stream, this);

			video_decoder.start();

			String audio = getParameter("audio"); // default: normal audio
			if (audio == null || audio.equalsIgnoreCase("yes") // try audio normal
			|| 	audio.equalsIgnoreCase("true")) { 
				System.out.println("TRYING AUDIO");
				audio_decoder.start();
			} else
				audio_decoder = null;
			movieScreen.init(mpeg_stream);

			if(! licenseVerified && ! video_license) {
				correctLicense = licenseVerified = true;
			}
		} else {
			saveItem.setEnabled(false);
			if(MovieScreen.Msg == null) {
				MovieScreen.ErrNum = 1;
				MovieScreen.Msg = "Failed to open URL";
			}
			movieScreen.myPaint();
		}

			
		movieScreen.appletStarted = true;
	}

	/**
	 *  Stop the whole player.
	 */
	public void stop() {
		System.out.println("MPEG1::stop()");
		playerStop();
		filename = null;
		firstLoop = true;
		if(input_reader != null)
			input_reader.close();
		input_reader = null;
		movieScreen.currentBufferingPercent = 0;
		buffered = false;
	}

	/**
	 * Gets the filename of the video file.
	 * @return the filename of the video file.
	 */
	public String get_filename() {
		return filename;
	}

	/**
	 * Gets the length of the video file in bytes.
	 * @return the length of the video file.
	 */
	public int get_video_size() {
		return video_length;
	}

	/**
	 * Stub for method that gets the length of the movie file in milliseconds.
	 * @return -1.
	 */
	public int get_video_length() {
		return -1;
//		return video_length;
	}

	/**
	 * Returns the mute value of the audio player.
	 * @return the mute value of the audio player.
	 */
	public boolean get_audio_state() {
		return mute;
	}

	/**
	 * Returns the code of last error.
	 * @return the code of last error.
	 */
	public int get_errorcode() {
		if(movieScreen.movieState == MovieScreen.LOCKED 
		|| movieScreen.movieState == MovieScreen.L_STOPPING)
			return 10;
		return MovieScreen.ErrNum;
	}
	
	/**
	 * Returns the state of the player (buffering/ready/playing/paused/finished/error).
	 * @return the state of the player (buffering/ready/playing/paused/finished/error).
	 */
	public String get_mf_state() {
		if(movieScreen.movieState == MovieScreen.ERROR)
			return "error";
		if(movieScreen.movieState == MovieScreen.LOCKED 
		|| movieScreen.movieState == MovieScreen.L_STOPPING) 
			return "error";
		if (!buffered)
			return "buffering";
		if (movieScreen.movieState == MovieScreen.REBUFFERING)
			return "re-buffering";
		if (movieScreen.movieState == MovieScreen.ENDED)
			return "finished";
		if (movieScreen.movieState == MovieScreen.PAUSED)
			return "paused";
		if (movieScreen.movieState == MovieScreen.PLAYED)
			return "playing";
		if (movieScreen.movieState == MovieScreen.STARTUP)
			return "ready";
		return "error";
	}

	/**
	 *  Destroys the player.
	 */
	public void destroy() {
		System.out.println("MPEG1::destroy()");
		System.out.flush();

		movieScreen.movieDestroy = true;

		// stop playing in the moviescreen
		playerStop();
	}
	
	/**
	 * Starts the movie playback.
	 */
	public void playerplay() {

		if (movieScreen.movieState == MovieScreen.PLAYED
			|| movieScreen.movieState == MovieScreen.ENDED
			|| movieScreen.movieState == MovieScreen.L_STOPPING
	 	    || movieScreen.movieState == MovieScreen.LOCKED
			|| movieScreen.movieState == MovieScreen.REBUFFERING)
			return;

		if (rewinded && !readyToPlay)
			return;

		if (!buffered) {
			autoPlay = true;
			return;
		}

		playing = true;
		playItem.setLabel("Pause");

		movieScreen.movieState = MovieScreen.PLAYED;
		if(movieScreen.startPlayingTime == -1)
			movieScreen.startPlayingTime = new Date().getTime();

		movieScreen.startTime = new Date().getTime();

		if (controlPanel != null)
			controlPanel.play();

		//	small delay with purpose to redraw the control panel
		try {
			Thread.sleep(100);
		} catch (Exception ex) {}

		movieScreen.playing = true;

		if (audio_decoder != null)
			audio_decoder.play();

		if (controlPanel != null)
			controlPanel.play();

		if (audio_decoder != null) {
			audio_decoder.setMute(mute);
			audio_decoder.setVolume(current_volume);
		}

	}

	/** <tt>True</tt> if the buffering of the movie file has been finished. */
	public boolean buffered = false;

	/** <tt>True</tt> if the {@link MPEG1#set_dim set_dim()} method was called by video_decoder. */
	private boolean initialized = false;

	/**
	 * Is invoked after buffer is filled enough to start the movie playback
	 * (when the size of the buffered data equals or greater than the value specified by the "pre_buffer" applet's parameter).
	 * @param reader the <code>MPEG_Reader</code> object ("MPEG_Reader" thread owner).
	 */
	public void playerEndBuffer(MPEG_Reader reader) {
		
		if(reader != input_reader)
			return;


		buffered = true;

		movieScreen.myPaint();

		if (initialized) {

			System.gc();

			if (autoPlay) {
				autoPlay = false;
				playerplay();
			} else {
				if (controlPanel != null)
					controlPanel.stop();
			}
		}

	}

	/**
	 * Starts the "rebuffering" of the player.
	 * @param reader the reader thread.
	 */
	public void startRebuffering(MPEG_Reader reader) {
		if(reader != input_reader)
			return;

		playItem.setEnabled(false);
		
		movieScreen.playing = false;
		movieScreen.currentBufferingPercent = 0;
		movieScreen.movieState = MovieScreen.REBUFFERING;

		if (controlPanel != null)
			controlPanel.disablePanel();
		if (audio_decoder != null)
			audio_decoder.pause();
			
	}

	/**
	 * Ends the "rebuffering" of the player.
	 * @param reader the reader thread.
	 */
	public void endRebuffering(MPEG_Reader reader) {
		if(reader != input_reader 
		|| movieScreen.movieState != MovieScreen.REBUFFERING)
			return;
			
		playItem.setEnabled(true);
		
		movieScreen.playing = true;
		movieScreen.movieState = MovieScreen.PLAYED;
		movieScreen.startTime = new Date().getTime();

		if (controlPanel != null)
			controlPanel.play();
		if (audio_decoder != null)
			audio_decoder.play();
			
	}
	
	/**
	 * Prints the "buffering" percent. 
	 * @param reader the <code>MPEG_Reader</code> object ("MPEG_Reader" thread owner).
	 * @param percent the "buffering" percent value.
	 */
	public void printBufferingPercent(MPEG_Reader reader, int percent) {
		if(reader != input_reader)
			return;

		movieScreen.currentBufferingPercent = percent;
		movieScreen.myPaint();
	}

	/**
	 * Pauses the movie playback.
	 */
	public void playerpause() {

		if (movieScreen.movieState != MovieScreen.PLAYED)
			return;
			
		playItem.setLabel("Play");

		movieScreen.movieState = MovieScreen.PAUSED;
		movieScreen.startTime = new Date().getTime();

		if (controlPanel != null)
			controlPanel.pause();

		movieScreen.playing = false;

		if (audio_decoder != null)
			audio_decoder.pause();

		if (controlPanel != null)
			controlPanel.pause();

	}

	/**
	 * Stops the movie playback. 
	 */
	public synchronized void playerStop() {

		playItem.setLabel("Play");
			
		if(movieScreen.movieState != MovieScreen.STARTUP 
		&&	movieScreen.movieState != MovieScreen.L_STOPPING
		&&  movieScreen.movieState != MovieScreen.LOCKED)
			movieScreen.movieState = MovieScreen.STOPPED;
			
		MPEG_video video_decoder = this.video_decoder;
		Player audio_decoder = this.audio_decoder;
		
		movieScreen.startTime = -1;

		movieScreen.myPaint();

		playItem.setEnabled(false);

		movieScreen.playing = false;

		movieScreen.startPlayingTime = -1;

		if(movieScreen.movieState != MovieScreen.L_STOPPING)
			this.video_decoder = null;
		this.audio_decoder = null;
			
		if (controlPanel != null)
			controlPanel.disablePanel();

		if (video_decoder != null)
			video_decoder.stop();

		if (audio_decoder != null)
			audio_decoder.stop();

		if (video_decoder != null)
			video_decoder.join();

		if (audio_decoder != null)
			audio_decoder.join();

/*
        if(mpeg_stream != null){
			try{
				mpeg_stream.close();
			} catch (IOException IE){}
		}
		
		if(audio_stream != null){
			try{
				audio_stream.close();
			} catch (IOException IE){}
		}
*/
		video_decoder = null;
		audio_decoder = null;
		mpeg_stream = null;
		audio_stream = null;

		Frame_nr = 0;
		last_P_or_I = 0;
	}

	/**
	 * Is invoked after the movie playback is finished. 
	 * @param video the <code>MPEG_Reader</code> object ("MPEG_Reader" thread owner).
	 */
	public synchronized void playerEnd(MPEG_video video) {
		
		if(video != video_decoder)
			return;

		playing = false;

		playItem.setLabel("Play");

		//	if(audio_decoder != null)
		//		audio_decoder.stop();
		if(movieScreen.movieState != MovieScreen.L_STOPPING 
		&&  movieScreen.movieState != MovieScreen.LOCKED)
			movieScreen.movieState = MovieScreen.ENDED;
		else
			movieScreen.movieState = MovieScreen.LOCKED;

		movieScreen.myPaint();

		if (controlPanel != null)
			controlPanel.endPlay();

		if (playbackLoop) {			
			playerlooprewind();
			wait_rewind();
			playerplay();
		}
	}

	/**
	 * Waits until the rewind action is finished.
	 */
	private void wait_rewind() {
		while (rewinded && readyToPlay == false) {
			try {
				Thread.sleep(100);
			} catch (Exception ex) {}
		}
		try {
			Thread.sleep(1000);
		} catch (Exception ex) {}
	}

	/**
	 * Rewinds the player to startup state after the movie playback is finished.
	 * This method could be invoked only if playbackLoop = true.
	 */
	private void playerlooprewind() {
		if (movieScreen.movieState == MovieScreen.STOPPED
			|| movieScreen.movieState == MovieScreen.STARTUP
			|| movieScreen.movieState == MovieScreen.LOCKED
			|| movieScreen.movieState == MovieScreen.L_STOPPING)
			return;

		playerStop();
		rewinded = true;
		readyToPlay = false;
		
		start();
		
		movieScreen.myPaint();

		System.gc();
	}

	/**
	 * Rewinds the player to the startup state.
	 */
	public void playerrewind() {
		if (movieScreen.movieState == MovieScreen.STOPPED
			|| movieScreen.movieState == MovieScreen.STARTUP
			|| movieScreen.movieState == MovieScreen.ERROR
			|| movieScreen.movieState == MovieScreen.LOCKED
			|| movieScreen.movieState == MovieScreen.L_STOPPING)
			return;
			
		playbackLoop = false;
		
		playerStop();
		rewinded = true;

		Frame_nr = 0;
		last_P_or_I = 0;

		start();
		
		if (controlPanel != null)
			controlPanel.stop();

		movieScreen.myPaint();

		System.gc();
	}
	
	/** 
	 * Sets the "mute" menu item state to 'On' or 'Off'
	 * @param mute the mute value.
	 */
	private void setMuteItemState(boolean mute) {
		if(mute) {
			muteItem.setLabel("un-mute");
		} else {
			muteItem.setLabel("mute");
		}
	}

	/**
	 * Enable or disable speaker of the player.
	 * @param mute mute value (<tt>true</tt> - disable sound, <tt>false</tt> - enable sound).
	 */
	public void playersetmute(boolean mute) {
	
		if(isSoundEnabled()) {
		
			this.mute = mute;

			if (controlPanel != null)
				controlPanel.setMute(mute);

			if (audio_decoder != null)
				audio_decoder.setMute(mute);
		}
		
		setMuteItemState(this.mute);
	}

	/**
	 * Loads the new movie into the video player box.
	 * @param fileName the movie file name.
	 */
	public void mediaframe_load(String fileName) {

		if(movieScreen.movieState == MovieScreen.L_STOPPING)
			return;
			
		boolean played = false;
		if (movieScreen.movieState == MovieScreen.PLAYED
			|| movieScreen.movieState == MovieScreen.PAUSED) {
				played = true;
		}
		playerStop();

		if(played){
			try {
				Thread.sleep(300);
			} catch (Exception ex) {}
		}

		if(input_reader != null)
			input_reader.close();

		input_reader = null;

		movieScreen.screen = null; 
		movieScreen.source = null;
		movieScreen.Picture = null;
		
		movieScreen.currentBufferingPercent = 0;
		
		new_filename = fileName;
		firstLoop = true;
		firstAppletLoop = true;

		autoPlay = "auto_start".equalsIgnoreCase(getParameter("playback"));
		
		playbackLoop = "true".equalsIgnoreCase(getParameter("loop")) ||
						"yes".equalsIgnoreCase(getParameter("loop"));
			
		buffered = false;
		
		initialized = false;
		rewinded = true;

		start();
		
		movieScreen.myPaint();

		System.gc();
	}

	/**
	 * Gets the current connection speed. 
	 * @return the current connection speed.
	 */
	public int get_connection_speed() {
		return integer_speeds[detected_speed >= 0 ? detected_speed : 0];
	}
	
	/**
	 * Gets the current volume as an interger value (between 1>10). 
	 * @return the current volume as an interger value (between 1>10).
	 */
	public int get_volume() {
		return (int)(current_volume * 9.f + 1.5f);
	}

	/**
	 * If the <code>percent</code> equals '%' gets the current volume as a percent value
	 * (between 0..100) otherwise returns the volume as an interger value (between 1>10).
	 * @param percent the type of the requested value (equals '%' for the percent type).
	 * @return the current volume as a percent or an integer value.
	 */
	public int get_volume(String percent) {
		if(! "%".equals(percent))
			return get_volume();
		return (int )(current_volume * 100);
	}

	/**
	 * Sets the volume level of the player. 
	 * Accepts either an interger (between 1>10) or a percent (x%) value.
	 * @param sVolume the volume level.
	 */
	public void playersetvolume(String sVolume) {

		float volume = parseVolume(sVolume);
		
		current_volume = volume > 1 ? 1 : volume < 0 ? 0 : volume;

		if (audio_decoder != null)
			audio_decoder.setVolume(current_volume);
	}
	
	/**
	 * Parses the volume level and returns it's float representation.
	 * @param sVolume the volume level in percent (100%..0%) or integer (10..1) form.
	 * @return the float value of the volume in the range 1..0.
	 */
	private float parseVolume(String sVolume) {
		float volume = 1;
		if(sVolume != null && sVolume.length() > 0) {
			if(sVolume.charAt(sVolume.length() - 1) == '%') {
				// percent value
				try {
					volume = (float)Integer.parseInt(sVolume.substring(0, sVolume.length() - 1)) / 100.f;
				} catch (Exception ex) {}
			} else {				
				// integer value
				try {
					volume = (float)(Integer.parseInt(sVolume) - 1) / 9.f;
				} catch (Exception ex) {}
			}	
		}
		return volume;		
	}

	/** 
	 * Gets the state of the audio player.
	 * @return <tt>true</tt> if the audio player is able to play the sound.
	 */
	public boolean isSoundEnabled() {

		if (! audioEnabled)
			return false;
		
		return Player.isSoundEnabled();
	}
	/*
		public long getAudioPlayingTime() {
	
			if (audio_decoder != null && !audio_decoder.isStopped() && AudioFrame.getFirstAudioTime() != -1) {
	
				if (audio_decoder.getPosition() >= 0)
					return AudioFrame.getFirstAudioTime() / 90 + (long)audio_decoder.getPosition(); 
				else 
					return -1;
			} else
				return -1;
		}
	*/

	/**
	 * This method is necessary because the scanner must have the possibility
	 * to resize the applet and (possibly) the frame once he has recognized
	 * the dimensions of the MPEG frames.
	 * This method is called by the "MPEG_Reader".
	 * @param width the width in MPEG stream.
	 * @param height the height in MPEG stream.
	 * @param o_width the original width in MPEG stream.
	 * @param o_height the original height in MPEG stream.
	 */
	public void set_dim(int width, int height, int o_width, int o_height) {

		playItem.setEnabled(true);

		readyToPlay = true;
		rewinded = false;
		
		if (!firstAppletLoop)
			return;
			
		firstAppletLoop = false;

		w = width;
		h = height; // The width in MPEG stream
		o_w = o_width;
		o_h = o_height; // The original width

		int sc_x = o_width = window_x;
		int sc_y = o_height = window_y;

		movieScreen.myPaint();
		movieScreen.myPaint();

		// produce the "MAXELEMENT" "Elements":
		for (int i = 0; i < MAXELEMENT; i++) {
			EL_ARR[i] = new Element(this, cm, w, h, o_w, o_h, sc_x, sc_y);
		}

		if (buffered && !initialized) {

			System.gc();

			if (autoPlay) {
				autoPlay = false;
				playerplay();
			} else {
				if (controlPanel != null)
					controlPanel.stop();
			}
		}

		initialized = true;

	}

	/**
	 * The method "set_Pixels" expects the pixel data, the type and number
	 * of the next frame in display order to change the pixel values of
	 * the next image. It determins the number of the next frame in display
	 * order (which differes from coding order) and passes it to the
	 * <code>MovieScreen</code> by means of the "Dispatcher".
	 * @param Pixels the array of pixels as YUV values.
	 * @param f_idx the frame number in the movie.
	 * @param f_type the type of the frame.
	 * @param pts the time in milliseconds that this frame is to be displayed.
	 * @param Pic_rate the frame rate code.
	 * @throws InterruptedException if another process interrupts this thread.
	 */

	public void set_Pixels(
		int Pixels[][],
		int f_idx,
		int f_type,
		long pts,
		int Pic_rate)
		throws InterruptedException {

		Element elem = EL_ARR[Frame_nr];

		elem.Make_Picture(Pixels /*, f_idx, f_type */
		);

		elem.setPts(pts);
		elem.setPicRate(Pic_rate);

		// put the frame into the list in display order:
		if (f_type == MPEG_video.B_TYPE)
			// B - Frames are already in display order
			movieScreen.setElement(EL_ARR[Frame_nr]);
		else // a P or I frame; they are not necessary in display order
			{
			movieScreen.setElement(EL_ARR[last_P_or_I]);
			last_P_or_I = Frame_nr; // notice for later insertion;
		}

		if(movieScreen.nextFrame()) {
			movieScreen.myPaint();
		}

		//movieScreen.write(""+audio_decoder.getPosition(), 20, 20);

		Frame_nr = (Frame_nr + 1) % MAXELEMENT; // wrap around!

	}

	/**
	 * Stub, overwrites the "update()" method of Applet (does nothing).
	 * @param g the graphics context to paint.
	 */
	public void update(Graphics g) {
	}

	/**
	 * Gets the audio player/decoder object.
	 * @return the audio player/decoder object.
	 */
	public Player getAudioPlayer() {
		return audio_decoder;
	}

	// /**
	// * The method "paint()" is only called in error case. It displays
	// * the error message.
	// */
	//
	// added: startup is more then just a gray square... :) **/
	//	public void paint(Graphics g) {
	//			g.setColor(Color.white);
	//
	//			if (MovieScreen.Msg != null) {
	//				g.setColor(Color.white);
	//				g.drawString(MovieScreen.Msg, 1, 15);
	//			}
	//			else
	//			{
	//				if(movieScreen.preImage != null){
	//					g.drawImage(movieScreen.preImage,0,0,this);
	//				} else {
	//					g.setColor(Color.black);
	//					g.fillRect(0, 0, 10000, 10000); // big enough
	//					g.setColor(Color.white);
	//					g.drawString("LOADING...", 10, 20);
	//				}
	//			}
	//	}

	/**
	 * Invoked when a mouse button has been pressed on the applet's movie screen.
	 * @param e the event's data. 
	 */
	public void mousePressed(MouseEvent e) {
		if(!linux_platform && e.isPopupTrigger()) {
			popup.show(e.getComponent(), e.getX(), e.getY());
		}
	}

	/**
	 * Invoked when a mouse button has been released from the applet's movie screen.
	 * @param e the event's data. 
	 */
	public void mouseReleased(MouseEvent e) {
		if (e.isPopupTrigger() || ((e.getModifiers() & MouseEvent.BUTTON3_MASK) != 0)) {
			popup.show(e.getComponent(), e.getX(), e.getY());
		}
	}

	/**
	 * Invoked when the mouse clicks on the applet's movie screen.
	 * @param e the event's data. 
	 */
	public void mouseClicked(MouseEvent e) {
		if (e.isPopupTrigger())
			popup.show(e.getComponent(), e.getX(), e.getY());
		else if (e.getModifiers() == InputEvent.BUTTON1_MASK) {
			if (clickToPlay && movieScreen.movieState == MovieScreen.STARTUP)
				playerplay();
			else
				openTarget();
		}
	}

	/**
	 * Invoked when the mouse enters the applet's movie screen area.
	 * @param e the event's data. 
	 */
	public void mouseEntered(MouseEvent e) {
		if (rolloverToPlay && movieScreen.movieState == MovieScreen.STARTUP) {
			playerplay();
		} else if (float_controls && !show_controls) {
			show_controls = true;
			movieScreen.myPaint();
		}
	}

	/**
	 * Invoked when the mouse exits the applet's movie screen area.
	 * @param e the event's data. 
	 */
	public void mouseExited(MouseEvent e) {
		if (float_controls && show_controls) {
			int new_x = e.getX();
			int new_y = e.getY();
			Object component = movieScreen.getComponentAt(new_x, new_y);
			if (component == null
				|| !component.getClass().getName().equals(
					"mediaframe.mpeg1.controlpanel.ControlButton")) {
				show_controls = false;
				movieScreen.myPaint();
			}
		}
	}

	/**
	 * Opens an address url in the target ("self", "blank", "top", etc) window.
	 */
	private void openTarget() {
		if (target != null) {
			try {
				if(movieScreen.ErrNum == 10) {
					getAppletContext().showDocument(new URL(lockedInfo), target);
				} else {
					getAppletContext().showDocument(new URL(address), target);
				}
			} catch (java.net.MalformedURLException MUE) {
				System.out.println("MPEG1.mouseClicked: " + MUE);
			}
		}
	}

	/**
	 * Opens and returns the <code>InputStream</code> for a zip-compressed file.
	 * @param aIs the <code>InputStream</code> of the zip file.
	 * @return the <code>InputStream</code> of the zip-compressed file.
	 * @throws Exception if an I/O error occurs. 
	 */
	private InputStream getZipStream(InputStream aIs) throws Exception {
		ZipInputStream zis = new ZipInputStream(aIs);
		ZipEntry ze = zis.getNextEntry();
		if (ze == null)
			throw new Exception("No Entries in zip file");
		video_length = (int) ze.getSize();
		return zis;
	}

	/**
	 * Returns <tt>true</tt> if the license is correct.
	 * @return <tt>true</tt> if the license is correct.
	 */
	public boolean isCorrectLicense() {
		return correctLicense;
	}

	/**
	 * Returns <tt>true</tt> if the license is already verified.
	 * @return <tt>true</tt> if the license is already verified.
	 */
	public boolean isLicenseVerified() {
		return licenseVerified;
	}

	/**
	 * Gets the image that embedded into applet's jar file by it's path. 
	 * @param image_path the path to the resource image. 
	 * @return the image which was loaded.
	 */
	private Image getEmbeddedImage(String image_path) {
		if(image_path == null) {
			return null;
		}
		InputStream is = getClass().getResourceAsStream(image_path);
		int c = 0;
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		Image image = null;
		try { 
			while((c = is.read()) != -1) {
				baos.write(c);
			}
			is.close();
			baos.close();
			image = Toolkit.getDefaultToolkit().createImage(baos.toByteArray());
		} catch (Exception ex) {
		}
		if(image != null) {
			MediaTracker mediaTracker = new MediaTracker(this);
			mediaTracker.addImage(image, 0);
			try {
				mediaTracker.waitForAll();
			} catch (Exception ex) {
			}
		}
		return image;
	}
}
